Welcome back.
In this lecture we're going to continue where we left off by scripting our leaderboard to save a player's
data in a data store.
Specifically, we want to save the player's kills and deaths.
So when they rejoin the game in the future, we can get their data so we can go ahead and get the data
store service.
And then we can go ahead and create a data store for our players.
If you would like to have the players kills and deaths be ordered, then you'll have to create two ordered
data stores, one for player deaths and another for player kills.
However, I am not concerned about ordering the data, so I'm just going to store the player's data
in one data store and I'm going to call it my player leaderboard.
Data store.
And that's going to be equal to data store service.
And we'll get a new data store and we'll call it player stats.
And as we've seen in a previous lecture, we're also going to want to keep track of any players that
may have errored when we try to grab their data.
So we'll create a table called Errored players, and we'll store them in there to prevent us from saving
any data and overwriting their previous data so we can, you know, prevent data loss.
So now we can go ahead and create a few functions.
One we can have for saving a player's data.
We'll call it save data for.
And we'll pass a player.
We'll also want to have a function for grabbing the data for a player.
So we could call this get data for and then pass a player.
And then we could also have a function for saving the data of every single player in our game.
So that would be save data for all players.
And this one would just simply loop through all of the players in our game and just call this function
on them.
So we would loop through every single player in players get players.
And what we want to do is we want to go ahead and save the data for this player.
But of course, since data stores are going to yield, we'll want to spawn this in a separate thread.
So that way we're not yielding in this for loop.
The next thing we could go ahead and do is we could go ahead and listen to when a player leaves our
game.
So player removing, we'll connect and get our player.
And what we can go ahead and do is just basically call the save data for function and pass our player.
So let's go ahead and actually fill this function out.
What data do we want to save for a particular player.
Well we want to go ahead and save their kills and deaths.
So that means we're going to use the player leaderboard data store and use the update async function
on this player.
And we're going to use their user ID as the key.
And of course we need to pass a function to this data store.
And it's going to give us the old data at this key.
And because we're using an asynchronous function, you need to remember like we did last time, we have
to wrap this in a p call.
So we'll have success.
And then error that's equal to p call.
And then we can just take this and paste that in there.
Now, if this is a new player where saving data for, of course they're not going to have any old data.
So if we do not have any old data, then we can go ahead and give them the default template for saving
data.
It'll just be a table.
And inside of here we can have a key one for kills which will be zero by default.
And then we'll have a key for deaths, which is also zero by default.
Then what we can go ahead and do is get the kills and deaths for our player.
So the kills is going to be equal to our players Leaderstats folder.
And we'll get the kills int value and get the value stored in there.
And then we can also go ahead and get the deaths for our player.
And it's equal to player dot Leaderstats dot deaths dot value.
And now inside of the old data table, we can go ahead and update the keys like kills, and we want
to set it equal to the kills stored in our leader stats.
Now let's say just in case, for some reason the kills value here is somehow less than the value stored
in old data.
Well, that means we don't want to overwrite it.
Our kills value shouldn't ever be less than what's already stored in the data store.
So we can put an if else statement here, and we can check if kills is greater than our old data dot
kills.
If it is, then we can go ahead and pass kills as the new value to set in this key.
Otherwise we don't want to overwrite it.
So we'll just pass the same data that was already there, and then we can copy this and do the exact
same thing.
But for our deaths.
So if our deaths is greater than all data kills.
We need to swap that to deaths.
Then we can go ahead and pass the new deaths of the player.
Otherwise we'll pass the old deaths of the player.
So this is an if else block that we can put in as a condition to check something.
And then we can go ahead and return a value based on this condition.
So if the player's current kills are greater than their old kills, then great will update it with that
new kills value.
Otherwise we'll leave the data the same.
And the same goes for the deaths.
If the deaths are greater than what was previously stored, will update it with this new value.
Otherwise we'll keep the old value.
And then last but not least, we need to return this data back to our data store.
Now if we aren't successful.
And saving this data, then we can give out a warning like we failed to save data for player, and then
we could just give the error message for what exactly happened.
And then something else we could do is we could also return the success boolean from this function.
And then we could go ahead and grab that right here.
And what we can do here is if a player joins our game and for some reason we fail to save their data,
then we can go ahead and have like a repeat loop continually executing this function, uh, maybe a
maximum of five times until we either save the player's data or we go beyond a certain threshold.
So if we were not successful with saving this player's data, then I'm going to create a variable.
I'll call it tries, set it equal to zero.
And we're going to continually repeat trying to save the data for this player until we are successful
in saving the player's data.
So we could do success is equal to what is returned from this function.
Or if our tries variable goes beyond the value of like five.
So each time through this loop we want to yield because we don't want to overwhelm our data store.
We'll yield for like 30s.
And then we'll also increment our tries by one each time.
So this will attempt to save the data for our player five times if we fail the first time.
And if we're never successful, then you know, oh well, that sucks.
We lost this player's data.
But you know that isn't too bad because it's just one game session.
But now that we have this setup, we can go ahead and also grab data from our data store to update the
values stored in our int values for the player.
So inside of the get data for function, what we can go ahead and do.
As get the success and result from our P call, and this time we want to refer to our player leaderboard
data store and we want to get async.
We want to get the data for a particular player and we'll return that.
And if we were not successful in grabbing this player's data, then we can go ahead and say something
like error.
And countered when trying to grab player data, and we'll pass the error into the console.
And then we can go ahead and mark this player as errored inside of our Errored players table.
So Errored players will use their name as the key.
Set that equal to true.
And then we'll just set result equal to a default table with kill set to zero and deaths set to zero.
And then we can go ahead and return result at the end of this function.
So now when our player joins the game, we want to go ahead and update the values inside of here to
reflect the data for this player.
So we can get their data.
And that's going to be equal to.
Get data for and we'll pass the player.
And then now that we have this data, when we create the int value of kills, we can go ahead and set
this value equal to our data and get kills.
And then for their deaths we can do the same thing, get their data and their deaths.
Now, another important thing that we make sure we do is that inside of our save data for function,
we want to check if the player were saving data for is not an errored player.
So if this player is inside of our Errored players table, so we'll pass player dot name.
If they're in there, then we need to return and we can give a warning like.
Unable to save data for.
An Erard player.
And then we'll make sure to return.
Another thing we need to make sure of is that this player isn't a fake player or a player from when
we do a local server testing.
So remember, we need to check if this player's user ID.
If it's less than one, then we don't want to save data for them because that will screw up our data
store.
We don't want to have a key that is a negative value.
That's just it's just not going to work.
And then another thing we want to do is when a player does leave our game, we want to make sure if
they were in error player, then we remove them from the table.
So actually the easiest way we could do this is just refer to our players.
And get the key with this player's name and just set it equal to nil, and that virtually just removes
it from our table.
Now another two important things we need to do with our data stores is we want to intermittently save
a player's data maybe every five minutes.
And then we also want to save the data for all the players in our game when, for example, a server
shuts down.
So we're going to use the bind to close function and pass a function here.
And this is where we would call our save data for all players.
So when our game is shutting down we'll make sure to save all of our data.
And then we'll also have a while loop down here.
And what we can do is we can wait like five minutes.
So that will be 300 seconds.
So every five minutes we're saving data for all of the players in our game.
And this is how we set up the data store for our leaderboard.
So let's go ahead and try it out.
If we play our game.
Um, we are getting an error, and that's because we have not enabled access to API services.
So let me actually go to my game settings and go to security and let me enable studio access to API
services.
So we can actually test this out.
And let me try playtesting it again.
And we're still getting this error for some reason.
So maybe it'll take just a second for it to actually update.
Let me make sure to just save it to Roblox just in case.
Or maybe I'll publish it to Roblox too, just in case.
And now let me see if we can go ahead and test it out.
And we did get an error here.
Actually attempt to index nil with kills.
So in our get data for function.
We are assuming that there's actually going to be a result here.
So when a player first joins our game, if we successfully grab data from the data store and this is
their first time joining the game, that means result is going to be nil.
So if result is nil, then we need to make sure to set it to this default value right here.
So what I'm going to do is I'm actually going to grab this.
And then we could have another if statement.
So if you were not successful or we do not have a result, then we can go ahead and set result equal
to this default table.
My bad on that one.
But now we should be good to go to playtest our game.
Perfect.
No errors in the console.
As you can see here I am in my game, and if I go ahead and reset and I die now, I have a death value
of one.
And then if I were to leave my game, it should save the data for my player.
And then if I go back and play test my game again.
Now, as you can see, my deaths have been saved on my player's character.
We can do the same thing for our kills.
If I go to the server and I get to my player and I update their kills value to something like ten.
And then I go back to the client and I were to leave the game.
It should have saved.
And then when I go and play, test it again.
As you can see now I have my value of ten back in my leaderboard as well as my death of one.
Now, let's say we fail to load our data for some reason, and our kills are less than what is stored
in our data store.
So if I go to this server and let's say I update my kills back to zero.
And if I try to leave the game, it shouldn't override my kills value because it's checking if the value
stored in my data store is less than the value stored in the game, so it shouldn't have saved my data.
And if I go back and play the game, as you can see, I still have my ten kills.
Very cool.
So now we have successfully set up our data stores to properly retrieve data when our player joins the
game.
We'll also save data for them when they leave the game, and we also save data intermittently every
five minutes, as well as when the server shuts down and when a player leaves our game.
We also make sure that if they were an Arab player, we clean up any values in our Arab player's table,
so we're not using any memory unnecessarily.
Great work so far on this project and I'll see you in the next lecture.